#include "thread.h"
#include "conductor.h"
#include "interrupt.h"
#include "assert.h"

/**
 * function [ mx_thread_stack_init ] implementation
 * @prototype: mx_void_pt mx_thread_stack_init (mx_void_pt stack, mx_usize_t size, mx_thread_entry_t entry, mx_thread_boot_t boot, mx_thread_pt thread);
 *      @param stack: the start address of stac
 *      @param size: the size of stack memory s
 *      @param entry: the entry point of thread
 *      @param boot: the boot handler of thread
 *      @param thread: the descriptor of thread
 *      @return: the bottom of stack
 * @desc: save the context of thread at the bottom of the stack, and return the bottom address of the stack
 */
extern mx_void_pt mx_thread_stack_init (mx_void_pt stack, mx_usize_t size, mx_thread_entry_t entry, mx_thread_boot_t boot, mx_thread_pt thread);

/**
 * function [ mx_thread_switch ] implementation
 * @prototype: void mx_thread_switch (mx_void_pt from, mx_void_pt to);
 *      @param from: the stack of source thread
 *      @param to: the stack of target thread
 *      @return: void
 * @desc: thread switching in non-interrupt environment
 */
extern void mx_thread_switch (mx_void_pt from, mx_void_pt to);

/**
 * function [ mx_thread_switch_interrupt ] implementation
 * @prototype: void mx_thread_switch_interrupt (mx_void_pt fromcontext, mx_void_pt from, mx_void_pt to);
 *      @param fromcontext: the context of from thread
 *      @param from: the stack of source thread
 *      @param to: the stack of target thread
 * @desc: thread switch in interrupt environment
 * @note: the interrupt in matrix-os can't be nesting
 */
extern void mx_thread_switch_interrupt (mx_void_pt fromcontext, mx_void_pt from, mx_void_pt to);

/* a flag indicate that need do schedule before exit the interrupt `mx_interrupt_leave`
   needn't use the `volatile` keyword for decoration */
static mx_bool_t interrupt_schedule_needed = mx_false;

/* an useless context storage buffer */
static mx_uint8_t dummy_context[ARCH_THREAD_CONTEXT_SIZE];

/* this is a dummy function, for compiler `bug` */
mx_weak void mx_thread_switch (mx_void_pt from, mx_void_pt to) { /* do nothing */ }

/**
 * do task schedule, it's invoked out of interrupt
 * @param: void
 * @note: subsequent code that calls this function is reachable
 * @note: this function allows for interrupt nesting
 * @note: this function is weak, for some platforms do schedule in interrupt like cortex-m4
 * @return: void, when "mx_thread_switch" returned, it's be another thread
 */
mx_weak void mx_thread_schedule ()
{
    mx_usize_t istate = mx_interrupt_disable_nest();
    /* get active thread */
    mx_thread_pt activetd = mx_conductor_active();
    /* get the highst priority thread */
    mx_thread_pt targettd = mx_conductor_highest();
    /* check whether the target thread is active thread */
    if (targettd != mx_null && targettd != activetd)
    {
        /* if the highest priority group has more than 1 thread, than need do time-slice switch */
        if (mx_conductor_hascrew(targettd))
        {
            /* the time status is pause means that the target thread is doing time-slice when be breaked */
            if (mx_timer_ispause(&targettd->timer))
            {
                mx_timer_start(&targettd->timer);
            }
            else
            {
                mx_timer_restart(&targettd->timer);
            }
        }

        /* change the status of target thread to running */
        mx_conductor_chstat(targettd, MX_THREAD_STATUS_RUNING);

        /* change the active thread to target thread */
        mx_conductor_chactive(targettd);

        /* two cases the activetd is null
           1. the system startup first time and don't do schedule in the 'mxboot' function
           2. the active thread dead and do schedule in 'thread_boot' function */
        if (activetd == mx_null)
        {
            /* full-descending stack, pre-decrement */
            mx_thread_switch((mx_void_pt)&dummy_context[ARCH_THREAD_CONTEXT_SIZE], targettd->stack);
            /* ATTENTION here is the reachable code after thread back */
        }
        else
        {
            /* if the status of active thread is runing, means that no special operations. e.g. sleep */
            if (activetd->status == MX_THREAD_STATUS_RUNING)
            {
                mx_conductor_chstat(activetd, MX_THREAD_STATUS_READY);
                /* if the timer status of active thread is enable, means it's do time slice cycle,
                   thus need pause the time of active thread */
                if (mx_timer_isenable(&activetd->timer))
                {
                    mx_timer_pause(&activetd->timer);
                }
            }
            /* do switch */
            mx_thread_switch(activetd->stack, targettd->stack);
            /* ATTENTION here is the reachable code after thread back */
        }
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * mark that need call the `mx_thread_schedule_interrupt` before exit the interrupt
 * @param: void
 * @return: void
 */
void mx_thread_schedule_interrupt_need ()
{
    interrupt_schedule_needed = mx_true;
}

/**
 * do task schedule in interrupt
 * @param context: the context of interrupted thread
 * @note: this function cannot be manually invoked by the user, it only called in `mx_interrupt_leave` by kernel
 * @note: this function doesn't allows for interrupt nesting, the interrupt will be enabled after exit interrupt
 * @return: void, this function never be return, thread switch in "mx_thread_switch_interrupt"
 */
void mx_thread_schedule_interrupt (mx_void_pt context)
{
    assert(context == mx_null);
    if (interrupt_schedule_needed)
    {
        /* reset the flag */
        interrupt_schedule_needed = mx_false;
        /* get active thread */
        mx_thread_pt activetd = mx_conductor_active();
        /* get the highst priority thread */
        mx_thread_pt targettd = mx_conductor_highest();
        /* check whether the target thread is active thread */
        if (targettd != mx_null && targettd != activetd)
        {
            if (mx_conductor_hascrew(targettd))
            {
                if (mx_timer_ispause(&targettd->timer))
                {
                    mx_timer_start(&targettd->timer);
                }
                else
                {
                    mx_timer_restart(&targettd->timer);
                }
            }

            /* change the status of target thread to running */
            mx_conductor_chstat(targettd, MX_THREAD_STATUS_RUNING);

            /* change the active thread to target thread */
            mx_conductor_chactive(targettd);
            
            /* two cases the activetd is null
            1. the system startup first time and don't do schedule in the 'mxboot' function
            2. the active thread dead and do schedule in 'thread_boot' function */
            if (activetd == mx_null)
            {
                /* full-descending stack, pre-decrement */
                mx_thread_switch_interrupt(context, (mx_void_pt)&dummy_context[ARCH_THREAD_CONTEXT_SIZE], targettd->stack);
            }
            else
            {
                /* if the status of active thread is runing, means that no special operations. e.g. sleep */
                if (activetd->status == MX_THREAD_STATUS_RUNING)
                {
                    mx_conductor_chstat(activetd, MX_THREAD_STATUS_READY);
                    /* if the timer status of active thread is enable, means it's do time slice cycle,
                    thus need pause the time of active thread */
                    if (mx_timer_isenable(&activetd->timer))
                    {
                        mx_timer_pause(&activetd->timer);
                    }
                }
                /* do switch */
                mx_thread_switch_interrupt(context, activetd->stack, targettd->stack);
            }
        }
    }
}

/**
 * boot the thread when the thread first execute, this function the real thread entry
 * and do the remedial work after thread exited
 * @param: the thread descriptor
 * @note: this function should place in the context of thread (break-point), and set
 *        the "call environment" to pass thread descriptor to it, ensure it be invoke
 *        the first time the thread run.
 * @return: void, this function never be return, thread switch in "mx_thread_schedule"
 */
static void thread_boot (mx_thread_pt thread)
{
    mx_int32_t exitcode;

    /* go to the entry-point of thread */
    exitcode = thread->entry(thread->param);

    mx_interrupt_disable();
    /* un-mount the timer of thread */
    mx_timer_umount(&thread->timer);
    
    /* change the status of dead thread to MX_THREAD_STATUS_DEAD */
    mx_conductor_chstat(thread, MX_THREAD_STATUS_DEAD);

    /* set the active thread to null */
    mx_conductor_chactive(mx_null);

    /* do schedule to switch to another thread,
       the interrupt don't need to be enabled because the this thread is DEAD and never be back */
    mx_thread_schedule();
}

/* the timer handler of thread */
static void thread_timer_handler (mx_timer_pt timer)
{
    mx_thread_pt thread = mx_thread_container(timer, timer);
    switch (thread->status)
    {
        /* case for time slice retation between the same priorities */
    case MX_THREAD_STATUS_RUNING:
        mx_thread_schedule_interrupt_need();    // set flag, mark that need do schedule in interrupt
        mx_timer_reset(timer);
        break;
        /* case for thread sleep */
    case MX_THREAD_STATUS_BLOCK:
        mx_thread_schedule_interrupt_need();    // set flag, mark that need do schedule in interrupt
        mx_conductor_chstat(thread, MX_THREAD_STATUS_READY);
        mx_timer_init(timer, thread->count);
        break;
    }
}

/**
 * mount a thread to the thread queue
 * @note: the thread status will be set with "MX_THREAD_STATUS_INIT"
 * @param thread: the thread descriptor
 * @param name: the thread name
 * @param priority: the thread priority, the smaller the value, the lower the priority
 * @param tick: time slice, switching time between threads of the same priority
 * @param stack: the stack start address of thread
 * @param size: the stack space size
 * @param entry: the entry point of thread
 * @param param: the parameter of thread
 * @note: this function allows for interrupt nesting
 * @return: void
 */
void mx_thread_mount (mx_thread_pt thread, char* name, mx_uint8_t priority, mx_usize_t tick,
                      mx_void_pt stack, mx_usize_t size, mx_thread_entry_t entry, mx_void_pt param)
{
    /* initialize property */
    thread->name = name;
    thread->priority = priority;
    thread->size = size;
    thread->entry = entry;
    thread->param = param;
    thread->count = tick;
    thread->bcount = MX_THREAD_BCOUNT_INITIAL;
    mx_dlist_init(&thread->list);
    /* initialize the initial stack structure */
    thread->stack = mx_thread_stack_init(stack, size, entry, thread_boot, thread);
    /* change the status of thread to init */
    mx_conductor_chstat(thread, MX_THREAD_STATUS_INIT);

    mx_usize_t istate = mx_interrupt_disable_nest();
    /* initialize the timer of thread for time slice switching */
    mx_timer_mount(&thread->timer, MX_TIMER_MODE_ONESHOT, tick, thread_timer_handler);
    /* insert the thread by conductor */
    mx_conductor_add(thread);
    mx_interrupt_enable_nest(istate);
}

/**
 * start up a specified thread, this thread will run immediately if it priority higher than active thread
 * @param thread: the thread descriptor
 * @note: this function allows for interrupt nesting
 * @return: void, it will not return if there are threads running, or just mark the specified thread 
 *          status is "MX_THREAD_STATUS_READY"
 */
void mx_thread_start (mx_thread_pt thread)
{
    mx_usize_t istate = mx_interrupt_disable_nest();
    /* change the status of specified thread to ready */
    mx_conductor_chstat(thread, MX_THREAD_STATUS_READY);

    /* we can't do schedule before initialization phase (until mx_boot function execution is completed) */
    if (mx_conductor_active() != mx_null)
    {
        mx_thread_schedule();   // ISSUE: mx_thread_schedule 函数内部关于中断的操作是否可以删除，并要求调用该函数时必须关闭中断
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * make current thread to sleep
 * @param ms: millisecond for sleep
 * @note: this function allows for interrupt nesting
 * @return: void
 */
void mx_thread_sleep (mx_usize_t ms)
{
    mx_usize_t istate = mx_interrupt_disable_nest();
    /* get active thread */
    mx_thread_pt activetd = mx_conductor_active();
    /* launch the timer of block to periodically wake-up self */
    mx_timer_launch(&activetd->timer, MX_TIMER_MS(ms));
    /* change this status of active thread to ready */
    mx_conductor_chstat(activetd, MX_THREAD_STATUS_BLOCK);
    /* do schedule, the interrupt will be enabled automatically */
    mx_thread_schedule();
    mx_interrupt_enable_nest(istate);
}

/**
 * make the specified thread to BLOCK, which status is RUNING or READY
 * @param pthread: the thread descriptor
 * @param tls: target list which you want to insert after suspend, it can be null
 * @param tick: delay tick that the thread in BLOCK status
 * @note: this function can be called in interrupt
 * @note: this function allows for interrupt nesting
 * @return: void
 */
void mx_thread_suspend (mx_thread_pt pthread, mx_dlist_pt tls, mx_usize_t tick)
{
    /* the mx_interrupt_enable_nest will not be called if this function called in interrupt
       but it doesn't matter */
    mx_usize_t istate = mx_interrupt_disable_nest();
    mx_uint8_t status = pthread->status;

    if (status == MX_THREAD_STATUS_RUNING || status == MX_THREAD_STATUS_READY)
    {
        /* change this status of active thread to ready */
        mx_conductor_chstat(pthread, MX_THREAD_STATUS_BLOCK);
        if (tick != 0)
        {
            /* launch the timer of block to periodically wake-up self */
            mx_timer_launch(&pthread->timer, tick);
        }
        else
        {
            if (mx_timer_isenable(&pthread->timer))
            {
                mx_timer_pause(&pthread->timer);
            }
        }
        /* insert to target list */
        if (tls != mx_null)
        {
            mx_dlist_insert_before(tls, &pthread->list);
        }
        /* do schedule when the target thread is runing */
        if (status == MX_THREAD_STATUS_RUNING)
        {
            /* do schedule */
            if (mx_interrupt_isentered())
            {
                mx_thread_schedule_interrupt_need();
            }
            else
            {
                mx_thread_schedule();
                /* ATTENTION here is the reachable code after thread back */
            }
        }
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * make the specified thread from BLOCK to READY
 * @param pthread: the thread descriptor
 * @note: this function can be called in interrupt
 * @note: this function allows for interrupt nesting
 * @return: void
 */
void mx_thread_resume (mx_thread_pt pthread)
{
    mx_usize_t istate = mx_interrupt_disable_nest();

    if (pthread->status == MX_THREAD_STATUS_BLOCK)
    {
        mx_conductor_chstat(pthread, MX_THREAD_STATUS_READY);
        /* close the block timer */
        if (mx_timer_isenable(&pthread->timer))
        {
            /* record break count */
            pthread->bcount = mx_timer_getcurrent(&pthread->timer);
            /* reset timer with count of time slice */
            mx_timer_init(&pthread->timer, pthread->count);
        }

        /* do schedule */
        if (mx_interrupt_isentered())
        {
            mx_thread_schedule_interrupt_need();
        }
        else
        {
            mx_thread_schedule();
            /* ATTENTION here is the reachable code after thread back */
        }
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * make all of thread's status which in the specified list from BLOCK to READY
 * @param list: the list of thread and theirs status are all BLOCK
 * @note: this function can be called in interrupt
 * @note: this function allows for interrupt nesting
 * @return: void
 */
void mx_thread_resumeall (mx_dlist_pt list)
{
    mx_usize_t istate = mx_interrupt_disable_nest();

    /* change all of thread'status in list to READY and remove it from `list` chain */
    mx_dlist_foreach_removable(list, l, {
        mx_thread_pt thread = mx_thread_container(l, list);
        /* check whether the status is BLOCK */
        if (thread->status == MX_THREAD_STATUS_BLOCK)
        {
            /* change the thread status to READY and mount it to READY list */
            mx_conductor_chstat(thread, MX_THREAD_STATUS_READY);
            /* close the weakup timer */
            if (mx_timer_isenable(&thread->timer))
            {
                /* record break count */
                thread->bcount = mx_timer_getcurrent(&thread->timer);
                /* reset timer with count of time slice */
                mx_timer_init(&thread->timer, thread->count);
            }
        }
    });
    /* do schedule */
    if (mx_interrupt_isentered())
    {
        mx_thread_schedule_interrupt_need();
    }
    else
    {
        mx_thread_schedule();
        /* ATTENTION here is the reachable code after thread back */
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * kill a thread no matter what status it is in, change status to DEAD
 * @param pthread: the thread descriptor
 * @note: this function can be called in interrupt
 * @note: this function allows for interrupt nesting
 * @return: void
 */
void mx_thread_kill (mx_thread_pt pthread)
{
    mx_usize_t istate = mx_interrupt_disable_nest();

    /* change the status of thread to dead */
    mx_conductor_chstat(pthread, MX_THREAD_STATUS_DEAD);
    /* un-mount the timer of thread */
    mx_timer_umount(&pthread->timer);
    if (pthread->status == MX_THREAD_STATUS_RUNING)
    {
        /* set the active thread to null */
        mx_conductor_chactive(mx_null);
        /* do schedule */
        if (mx_interrupt_isentered())
        {
            mx_thread_schedule_interrupt_need();
        }
        else
        {
            mx_thread_schedule();
            /* ATTENTION here is the reachable code after thread back */
        }
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * exit the current thread
 * @param: void
 * @note: this function can't be called in interrupt
 * @note: this function allows for interrupt nesting
 * @return: void
 */
void mx_thread_exit ()
{
    mx_usize_t istate = mx_interrupt_disable_nest();
    /* get active thread */
    mx_thread_pt activetd = mx_conductor_active();

    if (activetd != mx_null)
    {
        /* change the status of thread to dead */
        mx_conductor_chstat(activetd, MX_THREAD_STATUS_DEAD);
        /* un-mount the timer of thread */
        mx_timer_umount(&activetd->timer);
        /* set the active thread to null */
        mx_conductor_chactive(mx_null);
        /* do schedule */
        mx_thread_schedule();
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * voluntarily give up the cpu slice
 * @param: void
 * @note: this function can't be called in interrupt
 * @note: this function allows for interrupt nesting
 * @return: void
 */
void mx_thread_concede ()
{
    mx_usize_t istate = mx_interrupt_disable_nest();
    /* get active thread */
    mx_thread_pt activetd = mx_conductor_active();

    if (activetd != mx_null)
    {
        /* change the status of thread to ready */
        mx_conductor_chstat(activetd, MX_THREAD_STATUS_READY);
        /* reset timer to cancel the time-slice */
        mx_timer_reset(&activetd->timer);
        /* do schedule */
        mx_thread_schedule();
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * get the break count (bcount) of thread and reset it
 * @param pthread: the thread descriptor
 * @return: break count (bcount) of thread
 */
mx_usize_t mx_thread_bcountc (mx_thread_pt pthread)
{
    mx_usize_t ret = pthread->bcount;
    pthread->bcount = MX_THREAD_BCOUNT_INITIAL;
    return ret;
}
